namespace ScrumSprintMonitor.UI.Wpf
{
	#region #using Directives

	using System;
	using System.Configuration;
	using System.Diagnostics;
	using System.Globalization;
	using System.IO;
	using System.Reflection;

	using Infrastructure;
	using Infrastructure.Configuration;

	#endregion

	public sealed class Settings
	{
		private static Settings defaultInstance;
		private readonly Configuration appConfig;
		private readonly KeyValueConfigurationCollection settings;

		public Settings()
		{
			var assembly = Assembly.GetEntryAssembly();

			if (assembly == null || DesignerProperties.GetIsInDesignMode(null))
			{
				// Must be in design-mode. Use a mock with the default application settings.

				IsReadOnly = true;

				var appSettings = new AppSettingsSection();
				this.settings = appSettings.Settings;

				foreach (var propertyInfo in GetType().GetProperties())
				{
					var attributes = propertyInfo.GetCustomAttributes(typeof(DefaultSettingValueAttribute), false);
					if (attributes != null && attributes.Length == 1)
					{
						var attribute = (DefaultSettingValueAttribute) attributes[0];

						this.settings.Add(propertyInfo.Name, attribute.Value);
					}
				}
			}
			else
			{
				string binFolder = Path.GetDirectoryName(assembly.Location);
				string scrAssemblyLocation;

				string scrLocation = Path.Combine(binFolder, "ScrumSprintMonitor.Wpf.Application.scr");
				if (File.Exists(scrLocation))
				{
					scrAssemblyLocation = scrLocation;
				}
				else
				{
					string exeLocation = Path.Combine(binFolder, "ScrumSprintMonitor.Wpf.Application.exe");
					scrAssemblyLocation = exeLocation;
				}

				// Open App.Config of executable
				var fileMap = new ExeConfigurationFileMap {
				                                          	ExeConfigFilename = scrAssemblyLocation + ".config"
				                                          };

				if (!File.Exists(fileMap.ExeConfigFilename))
				{
					throw new InvalidOperationException(string.Format("Configuration file is missing at {0}.", fileMap.ExeConfigFilename));
				}

				// Test access level to the configuration file
				try
				{
					using (File.OpenWrite(fileMap.ExeConfigFilename))
					{
						IsReadOnly = false;
					}
				}
				catch (UnauthorizedAccessException)
				{
					IsReadOnly = true;
				}

				this.appConfig = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None);
				this.settings = this.appConfig.AppSettings.Settings;
			}
		}

		public static Settings Default
		{
			get
			{
				if (defaultInstance == null)
				{
					defaultInstance = new Settings();
				}

				return defaultInstance;
			}
		}

		public bool IsReadOnly { get; private set; }

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("https://tfs08.codeplex.com:443")]
		public string TfsServerName
		{
			get { return ((this.settings["TfsServerName"].Value)); }
			set
			{
				GuardWriteAttemptOnReadOnly();
				this.settings["TfsServerName"].Value = value;
			}
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue(".\\config\\SprintConfiguration.txt")]
		public string ConfigurationFilePath
		{
			get { return ((this.settings["ConfigurationFilePath"].Value)); }
			set
			{
				GuardWriteAttemptOnReadOnly();
				this.settings["ConfigurationFilePath"].Value = value;
			}
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue(".\\config\\PresentationConfiguration.txt")]
		public string PresentationConfigurationFilePath
		{
			get { return ((this.settings["PresentationConfigurationFilePath"].Value)); }
			set
			{
				GuardWriteAttemptOnReadOnly();
				this.settings["PresentationConfigurationFilePath"].Value = value;
			}
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue(".\\config\\team-info.xml")]
		public string TeamMembersConfigurationFilePath
		{
			get { return ((this.settings["TeamMembersConfigurationFilePath"].Value)); }
			set
			{
				GuardWriteAttemptOnReadOnly();
				this.settings["TeamMembersConfigurationFilePath"].Value = value;
			}
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("True")]
		public bool EnableOutlookIntegration
		{
			get { return (Convert.ToBoolean(this.settings["EnableOutlookIntegration"].Value, CultureInfo.InvariantCulture)); }
			set
			{
				GuardWriteAttemptOnReadOnly();
				this.settings["EnableOutlookIntegration"].Value = value.ToString(CultureInfo.InvariantCulture);
			}
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("False")]
		public bool ConsiderOutlookTentativeAsFreeTime
		{
			get { return (Convert.ToBoolean(this.settings["ConsiderOutlookTentativeAsFreeTime"].Value, CultureInfo.InvariantCulture)); }
			set
			{
				GuardWriteAttemptOnReadOnly();
				this.settings["ConsiderOutlookTentativeAsFreeTime"].Value = value.ToString(CultureInfo.InvariantCulture);
			}
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("True")]
		public bool ReportUnestimatedSprintBacklogItems
		{
			get { return (Convert.ToBoolean(this.settings["ReportUnestimatedSprintBacklogItems"].Value, CultureInfo.InvariantCulture)); }
			set
			{
				GuardWriteAttemptOnReadOnly();
				this.settings["ReportUnestimatedSprintBacklogItems"].Value = value.ToString(CultureInfo.InvariantCulture);
			}
		}

		[ApplicationScopedSetting]
		[DebuggerNonUserCode]
		[DefaultSettingValue("Sprinting")]
		public SprintMode ActiveSprintMode
		{
			get { return (SprintMode) Enum.Parse(typeof(SprintMode), this.settings["ActiveSprintMode"].Value); }
			set
			{
				GuardWriteAttemptOnReadOnly();
				this.settings["ActiveSprintMode"].Value = value.ToString();
			}
		}

		public void Refresh()
		{
			defaultInstance = null;
		}

		private void GuardWriteAttemptOnReadOnly()
		{
			if (IsReadOnly)
			{
				throw new InvalidOperationException(string.Format("The configuration file is read-only.", this.appConfig.FilePath));
			}
		}

		public void Save()
		{
			GuardWriteAttemptOnReadOnly();

			// Save the changes in App.config file.
			this.appConfig.Save(ConfigurationSaveMode.Modified);

			// Force a reload of a changed section.
			ConfigurationManager.RefreshSection("appSettings");
		}
	}
}